#include <sys/time.h>
#include <stdio.h>

#include <string>
#include <vector>
#include <memory>
#include <iosfwd>
#include <iostream>
#include <fstream>

#include <thread>
#include <mutex>
#include <chrono>

#include <boost/format.hpp>

#include "logging.h"

namespace sorb { 
namespace logging {

using std::string;
using std::ostream;
using std::ios;
using std::cout;
using std::endl;

static const int ESIZE = 10;
static const int INITBSIZE = 2;
static const int SWITCH_INTERVAL = 3; //second
const char* LogLevelName[NUM_LOG_LEVELS] = { "TRACE ", "DEBUG ", "INFO  ", "WARN  ", "ERROR ", "FATAL ", };

class ExchangeBuf
{
public :
    ExchangeBuf() 
    :_buffer_ptr(new buffer_t{INITBSIZE*2})
    {
        for(int i=0; i<INITBSIZE; ++i) {
            auto element = ExchangeBuf::element_t{ESIZE};
            for(int j=0; j<ESIZE; ++j) {
                _buffer_ptr->at(i).push_back(string{});
            }
            _buffer_ptr->push_back(std::move(element));
        }
    }

    ~ExchangeBuf() {
    }

    void Append(string&& msg) {
        (*(_buffer_ptr))[_current_buf][_current_element] = std::move(msg);
        ++_current_element;
        if(_current_element == ESIZE) {
            ++_current_buf;
            _current_element = 0;
            if(_current_buf == _buf_size) {
                ++_buf_size;
                cout << "buffer is full, add another one" << endl;
                cout << "after add, current buf: " << _current_buf << "  current buf size: "  << _buf_size << endl;
                auto element = ExchangeBuf::element_t{ESIZE};
                for(int i=0; i<ESIZE; ++i) {
                    _buffer_ptr->at(_current_buf).push_back(string{});
                }
                _buffer_ptr->push_back(std::move(element));
            }
        }
    }

    void WriteToStream(std::ostream& os) {
        cout << "in WriteToStream" << endl;
        cout <<"current buffer: " << _current_buf<< "    current element: " << _current_element << endl;
        for(int i = 0; i <= _current_buf; ++i) {
            cout << "i is: " << i << endl;
            int element_len = ESIZE;
            if(i == _current_buf) {
                element_len = _current_element;
            }
            cout << "element length is: " << element_len << endl;
            for(int j = 0; j < element_len; ++j) {
                cout <<"writting msg: " << (*_buffer_ptr)[i][j] << endl;
                os << (*_buffer_ptr)[i][j] << "\n";
            }
        
        }
        os.flush();
        _current_buf = 0;
        _current_element = 0;
        return;
    }

    ExchangeBuf(ExchangeBuf&& other) {
        *this = std::move(other);
    }

    ExchangeBuf& operator=(ExchangeBuf&& other) {
        if(this != &other) {
            _buffer_ptr = other._buffer_ptr;
            other._buffer_ptr.reset();
            _current_buf = other._current_buf;
            _current_element  = other._current_element;
        }
        return *this;
    }

private :
    typedef std::vector<std::string> element_t;
    typedef std::vector<element_t> buffer_t;
    typedef std::shared_ptr<buffer_t> buffer_ptr_t;
    buffer_ptr_t _buffer_ptr;
    size_t _current_buf = 0;
    size_t _current_element = 0;
    size_t _buf_size = INITBSIZE;

};

static
LogLevel initLogLevel()
{
    if (::getenv("TCORB_LOG_TRACE"))
        return logging::TRACE;
    else if (::getenv("TCORB_LOG_DEBUG"))
        return logging::DEBUG;
    else
        return logging::INFO;
}

Logger::Logger()
:_impl(new Impl())
{
    _impl->run();
}

/*
void Logger::Init(const std::string& fileName) {
    Logger::_logger_ptr.reset(new Logger(fileName));
}
*/

//c++11 makes sure the following is singleton, we will see
//std::once_flag _once_flag;
//std::unique_ptr<Logger> Logger::_logger_ptr = nullptr;
Logger* Logger::getInstance() {
    //_logger_ptr.reset(new Logger{fileName}); 
    //std::call_once(_once_flag, [=] {_logger_ptr.reset(new Logger{fileName});});
    //return *_logger_ptr.get();
    static Logger logger{};
    return &logger;
}

static LogLevel g_logLevel = initLogLevel();
static std::string g_logFilePath{"sorb.log"};

LogLevel logLevel() {
    return g_logLevel;
}

void setLogLevel(LogLevel level) {
    g_logLevel = level;
}

void setLogFilePath(const std::string& new_path) {
    //fixme, check the dir exists.
    std::cout << "new path is: " << new_path << std::endl;
    g_logFilePath = new_path;
}

std::string getTimeStamp() {
    char buf[32] = {0};
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm tm_time;
    gmtime_r(&tv.tv_sec, &tm_time);
    snprintf(buf, sizeof(buf), "%4d-%02d-%02d %02d:%02d:%02d.%06ld",
        tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
        tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, tv.tv_usec);
    return std::string(buf);
}

Impl::Impl() 
:_ofs(g_logFilePath.c_str(), ios::binary|ios::out|ios::app),
 _log_buf(new ExchangeBuf), _write_buf(new ExchangeBuf)
{
}

Impl::~Impl() {
    _ofs.close();
}

void Impl::run() {
    cout << "start thread" << endl;
    std::thread t([this](){
        std::chrono::seconds switch_interval(SWITCH_INTERVAL);
        while(true) {
            {
                cout << "sleep started" << endl;
                std::this_thread::sleep_for(switch_interval);
                cout << "sleep finished" << endl;
                std::lock_guard<std::mutex> lock(this->_mutex);
                cout << "start to swap" << endl;
                this->swapBuf();
            }
            cout << "start to write" << endl;
            this->_write_buf->WriteToStream(_ofs);
            cout << "write done" << endl;
        }

    });
    cout << "thread started" << endl;
    t.detach();
    cout << "thread detached" << endl;
}

void Impl::_log(boost::format& fmt) {
    //could throw exception, it's better the boost::format provides a noexcept verion.
    //catch exception will slow down the system, fix it later.
    std::string msg = fmt.str();
    log(std::move(msg));
}

void Impl::log(std::string&& msg) {
    std::lock_guard<std::mutex> lock(_mutex);
    std::cout <<"appending message: " << msg << std::endl;
    _log_buf->Append(std::move(msg));
}

void Impl::swapBuf() {
    //the outer function acquire the lock and do the operation, otherwise we need recursive lock here.    
    //std::lock_guard<std::mutex> lock(_mutex);
    std::swap(_log_buf, _write_buf);
}

}// namespace sorb
}// namespace logging
